package gox

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"gitee.com/burningsong/golib/pkg/errx"
)

const (
	//注意DelayIfStillRunning与SkipIfStillRunning是有本质上的区别的，前者DelayIfStillRunning只要时间足够长，后续通知n次都会执行一次任务，只是可能前一个任务耗时过长，导致后一个任务的执行时间推迟了一点。SkipIfStillRunning会忽略这次执行任务的通知。
	SkipIfStillRunning  Mode = 1
	DelayIfStillRunning Mode = 2
)

type Mode int

// NotifyWorker 等待通知更新任务
// 目标任务任意时刻只能有一个在执行
// 与singleflight的区别是：并发执行的时候，singleflight共享一个结果，假如一个任务时间很长(并且任务开始后无法感知数据已更新)，
// 可能影响任务的结果已经发生，任务返回时，结果已经过时。NotifyWorker则不同，当为DelayIfStillRunning时，任务在运行过程中，
// 收到通知n>1时，任务执行完成后都会再执行1次(不是n次)，只要执行过程中收到通知，执行完毕后就会再执行一次，直到执行过程中没有收到通知为止。
// 并发执行的时候，当
type NotifyWorker struct {
	name string
	//通知的chan, 用于通知任务立即执行
	//SkipIfStillRunning时创建无缓冲的chan，收到通知时任务正在执行则忽略
	//DelayIfStillRunning创建带有缓存的chan，缓存大小为1，收到通知时任务正在执行则等待执行完毕，
	//接着会消费chan中的通知再执行一次任务。这样只要执行任务期间有新的通知也会更新到。
	notifyChan chan struct{}
	// 保活，存储协程意外退出
	keepAliveChan  chan struct{}
	shutdownSignal chan os.Signal
	// 程序收到退出信号
	isShutdown bool
	// 停止保活
	stopKeepAliveChan chan struct{}
	taskFunc          func()
	alertFunc         func(title, detail string)
}

func NewNotifyWorker(name string, mode Mode, taskFunc func(), alertFunc func(title, detail string)) *NotifyWorker {
	worker := &NotifyWorker{
		name:              name,
		keepAliveChan:     make(chan struct{}),
		shutdownSignal:    make(chan os.Signal, 1),
		isShutdown:        false,
		stopKeepAliveChan: make(chan struct{}),
		taskFunc:          taskFunc,
		alertFunc:         alertFunc,
	}
	if mode == SkipIfStillRunning {
		worker.notifyChan = make(chan struct{})
	} else if mode == DelayIfStillRunning {
		worker.notifyChan = make(chan struct{}, 1)
	}
	return worker
}

func (w *NotifyWorker) NotifyRunTask() {
	select {
	case w.notifyChan <- struct{}{}:
	default:
	}
}

func (w *NotifyWorker) Start() {
	signal.Notify(w.shutdownSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	w.keepAlive()
	w.run()
}

func (w *NotifyWorker) run() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				w.alert(fmt.Sprintf("%s worker run recover", w.name), fmt.Sprintf("%s NotifyWorker run recover error:%+v,msg:%s", w.name, err, errx.GetStack()))
			}
			if !w.isShutdown {
				time.Sleep(time.Second)
				w.keepAliveChan <- struct{}{}
			}
		}()

		for {
			select {
			case <-w.notifyChan:
				w.taskFunc()
			case <-w.shutdownSignal:
				w.isShutdown = true
				w.stopKeepAliveChan <- struct{}{}
				return
			}
		}
	}()
}

func (w *NotifyWorker) keepAlive() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				w.alert(fmt.Sprintf("%s worker run recover", w.name),
					fmt.Sprintf("%s keepAlive recover error:%+v,msg:%s", w.name, err, errx.GetStack()))
			}
		}()

		for {
			select {
			case <-w.keepAliveChan:
				w.run()
			case <-w.stopKeepAliveChan:
				w.alert(fmt.Sprintf("%s worker run closed", w.name),
					fmt.Sprintf("%s keepAlive exit", w.name))
				return
			}
		}
	}()
}

// Stop 主动停止
func (w *NotifyWorker) Stop() {
	w.shutdownSignal <- syscall.SIGHUP
}

func (w *NotifyWorker) alert(title, detail string) {
	if w.alertFunc != nil {
		w.alertFunc(title, detail)
	}
}
